package com.stottlerhenke.simbionic.editor;

import java.awt.Color;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

import com.stottlerhenke.simbionic.api.SB_VarType;
import com.stottlerhenke.simbionic.editor.gui.SB_Line;
import com.stottlerhenke.simbionic.editor.gui.SB_OutputBar;

public class SB_TypeManager {
	
	/**
	 * Stores the information about a single type.
	 * The _externalId is the official ID of the type.  It is saved in datafiles,
	 * and it is used for all communications with the engine.  All other "ID"s 
	 * referenced in the application are merely combo box indexes, which may
	 * theoretically change during runtime as types are added/removed.
	 */
	public static class TypeInfo
	{
		String		_name;			// unique SB name of the type
		String		_package;		// unique fully-qualified type name ("" for non-classes)
		SB_VarType	_id;			// the official id of the type
		int			_typeFlags;		// what type of type is this?
		int			_validFlags;	// where can this type be used?
		boolean		_isBase;		// is this a built-in type?
		boolean		_deleted;		// has this type been deleted?
	};
	
	public SB_TypeManager() {
		_types = new ArrayList();
		_nextClassBaseId = SB_VarType.kCLASS_START.getState();
		_nextClassUserId = SB_VarType.kClass.getState();
		_typeChangeListeners = new ArrayList();
	}
	
	/**
	 * Initialize with the built-in types.
	 */
	public void initialize()
	{
		addTypeWithId("void", SB_VarType.kVoid, 
				ETypeValid.kForRetVal.getState(), 
				ETypeType.kJavaType.getState(), true);

		addTypeWithId("float", SB_VarType.kFloat, 
				ETypeValid.kForConst.getState()|ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kSimBionicType.getState()|ETypeType.kJavaType.getState(), true);
		
		addTypeWithId("string", SB_VarType.kString,
				ETypeValid.kForConst.getState()|ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kSimBionicType.getState(), true);

		// entity isn't really a Java type, 
		// but it stands in for long since we can't have two types with the same id
		addTypeWithId("entity", SB_VarType.kEntity,
				ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kSimBionicType.getState()|ETypeType.kJavaType.getState(), true);	

		addTypeWithId("vector", SB_VarType.kVector,
				ETypeValid.kForConst.getState()|ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kSimBionicType.getState(), true);
		
		addTypeWithId("data", SB_VarType.kData,
				ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kSimBionicType.getState(), true);
		
		addTypeWithId("integer", SB_VarType.kInteger,
				ETypeValid.kForConst.getState()|ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kSimBionicType.getState()|ETypeType.kJavaType.getState(), true);
		
		addTypeWithId("boolean", SB_VarType.kBoolean,
				ETypeValid.kForConst.getState()|ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kSimBionicType.getState()|ETypeType.kJavaType.getState(), true);
		
		addTypeWithId("array", SB_VarType.kArray,
				ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kSimBionicType.getState(), true);
		
		addTypeWithId("table", SB_VarType.kTable,
				ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kSimBionicType.getState(), true);

		addTypeWithId("short", SB_VarType.kShort,
				ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kJavaType.getState(), true);
		
		addTypeWithId("byte", SB_VarType.kByte,
				ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kJavaType.getState(), true);
		
		addTypeWithId("char", SB_VarType.kChar, 
				ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kJavaType.getState(), true);
		
		addTypeWithId("double", SB_VarType.kDouble,
				ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kJavaType.getState(), true);

		addTypeWithId("any", SB_VarType.kAny,
				ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kSimBionicType.getState(), true);
	}
	
	/**
	 * Sets the next-id counter for base types back to 128.
	 */
	public void resetNextId(){
		_nextClassBaseId = SB_VarType.kCLASS_START.getState();
	}
	
	/*
	 * ************* Adding and Removing Types ************
	 */ 

	/**
	 * Creates a new enum type with the given name.
	 * PRECONDITION: name must be unique among existing types!
	 * @param name unique name of the new enum
	 * @param isBase true if enum is built-in
	 * @return the external ID of the new enum
	 */
	public SB_VarType addEnum(String name, boolean isBase)
	{
		SB_VarType	id = (isBase ? 
				SB_VarType.getTypeFromInt(_nextClassBaseId) : SB_VarType.getTypeFromInt(_nextClassUserId));
		
		addTypeWithId(name, id, 
				ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kEnumType.getState(), isBase);
		
		return id;
	}

	/**
	 * Creates a new class type.  If package matches a deleted type, 
	 * then the new class will reuse the id of the deleted class.
	 * PRECONDITION: name must be unique among existing types!
	 * @param name unique SB name of the new class
	 * @param packageName the fully-qualified package name of the class
	 * @param isBase true if class is core
	 * @return the external ID of the new class
	 */
	public SB_VarType addClass(String name, String packageName, boolean isBase)
	{
		SB_VarType id;
		
		// does a class with this name already exist?
		TypeInfo info = getTypeInfoByPackage(packageName, true);	
		if (info != null){
			assert(info._deleted);	// method precondition
			id = info._id;	// reuse the id of the deleted class
		}
		else
			id = (isBase ? SB_VarType.getTypeFromInt(_nextClassBaseId) : SB_VarType.getTypeFromInt(_nextClassUserId));
			
		addTypeWithId(name, id,
				ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
				ETypeType.kClassType.getState(), isBase, packageName);
		
		return id;
	}

	/**
	 * Renames the specified type.  NB: if the type is a class, and the new package matches that 
	 * of a deleted class in this project, the class's id will be changed to that of the deleted class,
	 * undeleting the class.
	 * @param id id of the type to rename (must already exist!)
	 * @param name the new name of the type
	 * @param packageName the fully-qualified package name of the type (classes only)
	 */
	public void updateTypeName(SB_VarType id, String name, String packageName)
	{
		TypeInfo info = getTypeInfoById(id);
		assert(info != null);

		TypeInfo deleted = getTypeInfoByPackage(packageName, true);	// will only find classes
		if ((deleted != null) && (deleted != info))
		{
			// new name matches a deleted class, so reuse the deleted class id
			addTypeWithId(name, deleted._id, info._validFlags, info._typeFlags, info._isBase, packageName);

			// delete the old id -- caller should have checked for references to it!
			removeTypeById(id, true);
		}
		else
		{
			TypeInfo oldTypeInfo = cloneTypeInfo(info);
			info._name = name;
			info._package = packageName;
			notifyTypeChanged(oldTypeInfo, info);
		}
	}

	/**
	 * For non-class types.
	 * @see #updateTypeName(SB_VarType, String, String)
	 */
	public void updateTypeName(SB_VarType id, String name){
		updateTypeName(id, name, "");
	}
	
	/**
	 * Adds the specified type with the specified ID.  If a type with that
	 * ID already exists, it is replaced by the new type.
	 * @param name unique name of the new type
	 * @param id unique ID of the new type
	 * @param validFlags specifies where the type may be used
	 * @param typeFlags the types of the new type
	 * @param isBase true if type is built-in
	 * @param packageName the fully-qualified package name of the type (ignored for non-classes)
	 */
	public void addTypeWithId(String name, SB_VarType id, int validFlags, int typeFlags, boolean isBase, String packageName)
	{
		boolean newType = false;
		TypeInfo oldTypeInfo = null;

		// if a type with this ID already exists, find and replace it
		TypeInfo info = getTypeInfoById(id, true);
		if (info == null)
		{
			// no match, create a whole new type
			info = new TypeInfo();
			info._id= id;
			_types.add(info);
			newType = true;
		}

		if (!newType && info._deleted)
		{
			// warn user about any funny business
			String msg = "Resurrecting deleted type '" + info._name + "'.";
			SB_OutputBar.getInstance().getBuild().addLine(new SB_Line(msg, Color.orange));
			newType = true;
		}
		
		if (!newType)
			oldTypeInfo = cloneTypeInfo(info);

		info._name = name;
		info._package = packageName;
		info._typeFlags = typeFlags;
		info._validFlags = validFlags;
		info._isBase = isBase;
		info._deleted = false;
		
		
		if (newType)
			// notified listeners that a new type was added
			notifyTypeAdded(info);
		else
			// notifies listeners that the type was changed
			notifyTypeChanged(oldTypeInfo, info);

		// move the next-id values so we don't reuse this id
		if ((id.getState() >= _nextClassBaseId) && (id.getState() < SB_VarType.kClass.getState()))
			_nextClassBaseId = id.getState() + 1;
		else if (id.getState() >= _nextClassUserId)
			_nextClassUserId = id.getState() + 1;
	}
	
	/**
	 * For non-class types.
	 * @see #addTypeWithId(String, SB_VarType, int, int, boolean, String)
	 */
	public void addTypeWithId(String name, SB_VarType id, int validFlags, int typeFlags, boolean isBase){
		addTypeWithId(name, id, validFlags, typeFlags, isBase, "");
	}

	/**
	 * Deletes the specified type so that it cannot be used in the project.
	 * The id of the type is reserved, however, so that if the type is recreated
	 * it will retain the same id.
	 * @param name unique name of the type to delete
	 */
	public void removeTypeByName(String name)
	{
		for (int i = 0; i < _types.size(); i ++)
		{
			if (((TypeInfo)_types.get(i))._name.equals(name))
			{
				deleteType(i);
				break;
			}
		}
	}

	/**
	 * Deletes the specified type so that it cannot be used in the project.
	 * The id of the type is reserved, however, so that if the type is recreated
	 * it will retain the same id.
	 * @param id id of the type to delete
	 * @param permanent if true, completely deletes the type and frees its id
	 */
	public void removeTypeById(SB_VarType id, boolean permanent)
	{
		for (int i = 0; i < _types.size(); i ++)
		{
			if (((TypeInfo)_types.get(i))._id.equals(id))
			{
				deleteType(i, permanent);
				break;
			}
		}
	}
	
	/**
	 * removeTypeById(id, false)
	 * @see #removeTypeById(SB_VarType, boolean)
	 */
	public void removeTypeById(SB_VarType id){
		removeTypeById(id, false);
	}

	/**
	 * Serializes all deleted types to the given stream.
	 * This is so that their type ids will be reserved and
	 * given back if the class is ever imported again.
	 */
	public void serialize(ObjectOutputStream out) throws IOException
	{
		ArrayList deleteds = new ArrayList();
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (type._deleted)	// only classes ever get marked as deleted
				deleteds.add(type);
		}

		out.writeInt(deleteds.size());

		for (int i = 0; i < deleteds.size(); i ++)
		{
			TypeInfo type = (TypeInfo) deleteds.get(i);
			out.writeObject(type._name);
			out.writeObject(type._package);
			out.writeInt(type._id.getState());
		}
	}
	
	public void deserialize(ObjectInputStream in) throws ClassNotFoundException, IOException
	{
		int size = in.readInt();
		for (int i = 0; i < size; i ++)
		{
			String name = (String) in.readObject();
			String packageName = (String) in.readObject();
			SB_VarType id = SB_VarType.getTypeFromInt(in.readInt());

			addTypeWithId(name, id,
					ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
					ETypeType.kClassType.getState(), false, packageName);
			removeTypeById(id);	// mark it as deleted
		}
	}

    
	/**
     * Changes the type ID for the given class, if possible.
     * @param classToChange
     * @param newTypeId
     * @return true if the change is successfully completed, false if not (ie the new ID is already in use)
	 */
    public boolean changeTypeId(SB_Class classToChange, int newTypeId)
    {
        SB_VarType newType = new SB_VarType(newTypeId);
        if (!getTypeName(newType).equals(""))
            return false;
        
        SB_VarType oldType = classToChange.getTypeId();
        TypeInfo info = getTypeInfoById(oldType);
        
        removeTypeById(oldType,true);   // permanently delete the old type id

        addTypeWithId(info._name, newType,
                ETypeValid.kForVar.getState()|ETypeValid.kForParam.getState()|ETypeValid.kForRetVal.getState(),
                ETypeType.kClassType.getState(), info._isBase, info._package);
        
        return true;
    }  

	/*
	 * ************* Looking Up Types ************
	 */

	/**
	 * Looks up the type ID for the named type.
	 * @param name the name to look up
	 * @return the type ID, or kNoSuchName if none is found
	 */
	public SB_VarType getTypeIdByName(String name){
		TypeInfo type = null;
		if ("int".equals(name))
			type = getTypeInfoByName("integer");
		else
			type = getTypeInfoByName(name);
		if (type != null)
			return type._id;
		else
			return SB_VarType.kNoSuchName;
	}

	/**
	 * Looks up the name for the given type id.
	 * @param id the type id to look up
	 * @return the type name, or "" if none is found
	 */
	public String getTypeName(SB_VarType id){
		TypeInfo type = getTypeInfoById(id);
		if (type != null)
			return type._name;
		else
			return "";
	}
	
	public String getTypePackage(SB_VarType id){
		TypeInfo type = getTypeInfoById(id);
		if (type != null)
			return type._package;
		else
			return "";
	}
	
	/**
	 * @param packageName fully-qualified name of the Java class to look up
	 * @return the class id (even if deleted), or kNoSuchName if the specified class has never existed in this project
	 */
	public SB_VarType getClassIdByPackage(String packageName){
		TypeInfo type = getTypeInfoByPackage(packageName, true);
		if (type != null)
			return type._id;
		else
			return SB_VarType.kNoSuchName;
	}

	/**
	 * Looks up the C++ type constant for the given type id.
	 * @param id the type id to look up
	 * @return the type constant, or kNoSuchName if none is found
	 */
	public String getCppTypeConstant(SB_VarType id){
		return null;//TODO
	}

	/**
	 * Converts the type id into a valid sim file id.  In most cases, there
	 * is no change, but this method catches illegal values and also converts
	 * tables into arrays.  It also replaces enumerated types with strings/integers.
	 * @param id the id to convert
	 * @return the converted id
	 */
	public int idToSimType(SB_VarType id)
	{
		// make sure the id actually still exists
		if (getTypeName(id) == "")
			return SB_VarType.kNoSuchName.getState();
		
		if ((id.getState() < 0) && (!id.equals(SB_VarType.kVoid)))
		{
			// report illegal type id
			String msg = "Illegal type id '" + id + "' published to sim file.";
			System.err.println(msg);
			throw new RuntimeException(msg);
		}
		else if (id.getState() >= SB_VarType.kCLASS_START.getState())
		{
			return getIdNoEnums(id).getState();
		}
		else if (id.equals(SB_VarType.kTable))
			return SB_VarType.kArray.getState();// engine doesn't know about tables

		return id.getState();
	}

	/**
	 * Converts enums into the corresponding string or integer type.
	 * @param id the id to convert
	 * @return the converted type id 
	 */
	public SB_VarType getIdNoEnums(SB_VarType id)
	{
		return id;//TODO
	}

	/*
	 * Old type ids
	 */
	private final static int OLD_INVALID_VT = -1;
	private final static int OLD_FLOAT_VT = 0;
	private final static int OLD_STRING_VT = 1;
	private final static int OLD_ENTITY_VT = 2;
	private final static int OLD_VECTOR_VT = 3;
	private final static int OLD_DATA_VT = 4;
	private final static int OLD_INTEGER_VT = 5;
	private final static int OLD_BOOLEAN_VT = 6;
	private final static int OLD_ANY_VT = 7;
	private final static int OLD_ARRAY_VT = 8;
	private final static int OLD_TABLE_VT = 9;
	private final static int OLD_VOID_VT = 10;

	/**
	 * Converts the old (SBP < 18) internal type IDs to current type IDs.
	 * @param oldId the old ID to be converted
	 * @return the equivalent external type ID
	 */
	static public SB_VarType convertOldTypeId(int oldId)
	{
		if (oldId >= SB_VarType.kCLASS_START.getState())
			return SB_VarType.getTypeFromInt(oldId);	// class and enum ids are unchanged

		switch (oldId)
		{
		case OLD_INVALID_VT: return SB_VarType.kInvalid;
		case OLD_FLOAT_VT: return SB_VarType.kFloat;	// used to be "number"
		case OLD_STRING_VT: return SB_VarType.kString;
		case OLD_ENTITY_VT: return SB_VarType.kEntity;
		case OLD_VECTOR_VT: return SB_VarType.kVector;
		case OLD_DATA_VT: return SB_VarType.kData;
		case OLD_INTEGER_VT: return SB_VarType.kInteger;
		case OLD_BOOLEAN_VT: return SB_VarType.kBoolean;
		case OLD_ANY_VT: return SB_VarType.kAny;
		case OLD_ARRAY_VT: return SB_VarType.kArray;
		case OLD_TABLE_VT: return SB_VarType.kTable;
		case OLD_VOID_VT: return SB_VarType.kVoid;
		default:
			System.err.println("Error converting old type ID '" + oldId + "'!");
			assert(false);
		}

		return SB_VarType.kNoSuchName;	// can't get here
	}

	/**
	 * Determines if one type can be cast to another.
	 * @param toType the type to be cast to
	 * @param fromType the type to be cast
	 * @return true if the cast is legal, false otherwise
	 */
	public boolean canCastTypes(SB_VarType toType, SB_VarType fromType)
	{
		if ((toType == SB_VarType.kAny) || (fromType == SB_VarType.kAny) || (toType == fromType))
			return true;

		// integers and floats are interchangeable
		if ((toType == SB_VarType.kFloat && fromType == SB_VarType.kInteger) ||
			(toType == SB_VarType.kInteger && fromType == SB_VarType.kFloat))
			return true;
		
		// tables are implemented as arrays
		if ((fromType == SB_VarType.kTable && toType == SB_VarType.kArray) ||
			(fromType == SB_VarType.kArray && toType == SB_VarType.kTable))
			return true;

		String toName = getTypeName(toType);
		String fromName = getTypeName(fromType);

		// arrays can be cast to Java vectors and vice versa
		if ((((fromType == SB_VarType.kArray) || (fromType == SB_VarType.kTable)) && (toName.equals("Vector"))) ||
			 ((fromName.equals("Vector")) && ((toType == SB_VarType.kArray) || (toType == SB_VarType.kTable))))
			return true;
		
		// arrays can also be cast to Java ArrayLists and vice versa
		if ((((fromType == SB_VarType.kArray) || (fromType == SB_VarType.kTable)) && (toName.equals("ArrayList"))) ||
			 ((fromName.equals("ArrayList")) && ((toType == SB_VarType.kArray) || (toType == SB_VarType.kTable))))
			return true;

		// entity is implemented as a long
		if (((toType == SB_VarType.kEntity) && (fromName.equals("Long"))) ||
			((toName.equals("Long")) && (fromType == SB_VarType.kEntity)))
			return true;

		// vector is implemented as SB_Vector
		if (((toType == SB_VarType.kVector) && (fromName.equals("SB_Vector"))) ||
			((toName.equals("SB_Vector")) && (fromType == SB_VarType.kVector)))
			return true;

		// classes can be cast to data and vice versa
		if (((toType == SB_VarType.kData) && (fromType.getState() >= SB_VarType.kCLASS_START.getState())) ||
			((toType.getState() >= SB_VarType.kCLASS_START.getState()) && (fromType == SB_VarType.kData)))
			return true;

		// assume classes can be cast to one another
		// check casting between classes using introspection
		if ((toType.getState() >= SB_VarType.kCLASS_START.getState()) && (fromType.getState() >= SB_VarType.kCLASS_START.getState()))
		{
			return true;
			// Currently, the parser doesn't support the syntax of explicit type casting,
			// e.g., MyClass c = (MyClass)list.get(i)
			// so we have to allow down-casting and let the Java report ClassCastException
			// once the syntax is supported, the following code makes more sense.
//			try{
//				String toFullName = getTypeInfoById(toType)._package;
//				String fromFullName = getTypeInfoById(fromType)._package;
//				Class toClass = Class.forName(toFullName);
//				Class fromClass = Class.forName(fromFullName);
//				return toClass.isAssignableFrom(fromClass);
//			}
//			catch (ClassNotFoundException ex){
//				return false;
//			}
		}

		// the boxed primitives can be cast to their primitive counterparts
		if ("boolean".equalsIgnoreCase(toName) && "boolean".equalsIgnoreCase(fromName))
			return true;
		else if ("byte".equalsIgnoreCase(toName) && "byte".equalsIgnoreCase(fromName))
			return true;
		else if ("char".equals(toName) && "Character".equals(fromName) || "Character".equals(toName) && "char".equals(fromName))
			return true;
		else if ("short".equalsIgnoreCase(toName) && "short".equalsIgnoreCase(fromName))
			return true;
		else if ("integer".equalsIgnoreCase(toName) && "integer".equalsIgnoreCase(fromName))
			return true;
		else if ("long".equalsIgnoreCase(toName) && "long".equalsIgnoreCase(fromName))
			return true;
		else if ("float".equalsIgnoreCase(toName) && "float".equalsIgnoreCase(fromName))
			return true;
		else if ("double".equalsIgnoreCase(toName) && "double".equalsIgnoreCase(fromName))
			return true;
		else if ("string".equalsIgnoreCase(toName) && "string".equalsIgnoreCase(fromName))
			return true;

		
		{
			int toTypeVal = getNumericTypeValue(toType);
			if (toTypeVal < 0)
				return false;
			
			int fromTypeVal = getNumericTypeValue(fromType);
			if (fromTypeVal < 0)
				return false;
			
			if (toTypeVal >= fromTypeVal)
				return true;
		}

		return false;
	}
	
	/**
	 * Returns whether the specified class exists or not.
	 * The class is identified by its full package name, e.g., 
	 * java.lang.String.
	 * @param packageName	full package name, e.g., java.lang.String
	 * @return	true if the class is registered to the type manager
	 */
	public boolean classExists(String packageName){
		return (getTypeInfoByPackage(packageName, false) != null);
	}
	
	
	/*
	 * ************* Combo Box Handling ************
	 */

	/**
	 * Generates the list of items for the type combo box for local and global variables,
	 * constants and behavior/action/predicate parameters.
	 * @param isConstant true if this is a combo box for constants
	 * @param isParam true if this is a combo box for a behavior parameter
	 * @return list of items for the variable combo box
	 */
	public ArrayList getVarComboItems(boolean isConstant, boolean isParam)
	{
		ArrayList items = new ArrayList();
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (isVarComboItem(type, isConstant, isParam))
			{
				// valid type
				items.add(type._name);
			}
		}

		return items;
	}
	
	/**
	 * getVarComboItems(false, false)
	 * @see #getVarComboItems(boolean, boolean)
	 */
	public ArrayList getVarComboItems(){
		return getVarComboItems(false, false);
	}

	/**
	 * @param index index of variable combo box item 
	 * @param isConstant true if this is a combo box for constants
	 * @param isParam true if this is a combo box for a behavior parameter
	 * @return external id of the item
	 */
	public SB_VarType varComboIndexToId(int index, boolean isConstant, boolean isParam)
	{
		int count=0;
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (isVarComboItem(type, isConstant, isParam))
			{
				// valid type; does the index match?
				if (count == index)
					return type._id;

				count++;
			}
		}
		
		return SB_VarType.kNoSuchName;
	}
	
	/**
	 * varComboIndexToId(index, false, false)
	 * @see #varComboIndexToId(int, boolean, boolean)
	 */
	public SB_VarType varComboIndexToId(int index){
		return varComboIndexToId(index, false, false);
	}

	/**
	 * @param id id of the type to convert
	 * @param isConstant true if this is a combo box for constants
	 * @param isParam true if this is a combo box for a function parameter
	 * @return combo box index of the item
	 */
	public int idToVarComboIndex(SB_VarType id, boolean isConstant, boolean isParam)
	{
		int count=0;
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (isVarComboItem(type, isConstant, isParam))
			{
				if (type._id.equals(id))
					return count;

				count++;
			}
		}

		return -1;
	}
	
	/**
	 * idToVarComboIndex(id, false, false)
	 * @see #idToVarComboIndex(SB_VarType, boolean, boolean)
	 */
	public int idToVarComboIndex(SB_VarType id){
		return idToVarComboIndex(id, false, false);
	}

	/**
	 * Generates the list of items for the type combo box for
	 * class fields and method parameters.
	 * @param isParam true if this is a combo box for a method param
	 * @return list of items for the class param/field combo box
	 */
	public ArrayList getClassComboItems(boolean isParam)
	{
		ArrayList items = new ArrayList();
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (isClassComboItem(type, isParam))
			{
				// valid type
				items.add(type._name);
			}
		}

		return items;
	}
	
	/**
	 * getClassComboItems(false)
	 * @see #getClassComboItems(boolean)
	 */
	public ArrayList getClassComboItems(){
		return getClassComboItems(false);
	}

	/**
	 * @param index index of class field/param combo box item 
	 * @param isParam true if this is a combo box for a method field/param
	 * @return external id of the item
	 */
	public SB_VarType classComboIndexToId(int index, boolean isParam)
	{
		int count=0;
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (isClassComboItem(type, isParam))
			{
				// valid type; does the index match?
				if (count == index)
					return type._id;

				count++;
			}
		}
		
		return SB_VarType.kNoSuchName;
	}
	
	/**
	 * classComboIndexToId(index, false)
	 * @see #classComboIndexToId(int, boolean)
	 */
	public SB_VarType classComboIndexToId(int index){
		return classComboIndexToId(index, false);
	}

	/**
	 * @param id id of the type to convert
	 * @param isParam true if this is a combo box for a method param
	 * @return combo box index of the item
	 */
	public int idToClassComboIndex(SB_VarType id, boolean isParam)
	{
		int count=0;
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (isClassComboItem(type, isParam))
			{
				// valid type; does the index match?
				if (type._id.equals(id))
					return count;

				count++;
			}
		}
		
		return -1;
	}
	
	/**
	 * idToClassComboIndex(id, false)
	 * @see #idToClassComboIndex(SB_VarType, boolean)
	 */
	public int idToClassComboIndex(SB_VarType id){
		return idToClassComboIndex(id, false);
	}

	/**
	 * Generates the list of items for the type combo box for function or method 
	 * return types.
	 * @param isMethod true if this is a combo box for a method return value
	 * @return list of items for the function return value combo box
	 */
	public ArrayList getReturnValueComboItems(boolean isMethod)
	{
		ArrayList items = new ArrayList();
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (isReturnValueComboItem(type, isMethod))
				items.add(type._name);
		}

		return items;
	}
	
	/**
	 * getReturnValueComboItems(false)
	 * @see #getReturnValueComboItems(boolean)
	 */
	public ArrayList getReturnValueComboItems(){
		return getReturnValueComboItems(false);
	}

	/**
	 * @param index index of function/method return value combo box item 
	 * @param isMethod true if this is a combo box for a method return value
	 * @return external id of the item
	 */
	public SB_VarType returnValueComboIndexToId(int index, boolean isMethod)
	{
		int count=0;
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (isReturnValueComboItem(type, isMethod))
			{
				// valid type; does the index match?
				if (count == index)
					return type._id;

				count++;
			}
		}
		
		return SB_VarType.kNoSuchName;
	}
	
	/**
	 * returnValueComboIndexToId(index, false)
	 * @see #returnValueComboIndexToId(int, boolean)
	 */
	public SB_VarType returnValueComboIndexToId(int index){
		return returnValueComboIndexToId(index, false);
	}

	/**
	 * @param id id of the type to convert
	 * @param isMethod true if this is a combo box for a method return value
	 * @return combo box index of the item
	 */
	public int idToReturnValueComboIndex(SB_VarType id, boolean isMethod)
	{
		int count=0;
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (isReturnValueComboItem(type, isMethod))
			{
				if (type._id.equals(id))
					return count;

				count++;
			}
		}
		
		return -1;
	}
	
	/**
	 * idToReturnValueComboIndex(id, false)
	 * @see #idToReturnValueComboIndex(SB_VarType, boolean)
	 */
	public int idToReturnValueComboIndex(SB_VarType id){
		return idToReturnValueComboIndex(id, false);
	}
	
	public String[] getPackageItems(){
		return getPackageItems(false, false);
	}
	
	public String[] getPackageItems(boolean isConstant, boolean isParam)
	{
		HashMap items = new HashMap();
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (type._id.getState() >= SB_VarType.kCLASS_START.getState() &&
					isVarComboItem(type, isConstant, isParam))
			{
				// valid type
				int idx = type._package.lastIndexOf('.');
				String packageName = (idx < 0 ? "(default package)" : type._package.substring(0, idx));
				items.put(packageName, packageName);
			}
		}
		String[] pItems = new String[items.values().size()];
		items.values().toArray(pItems);
		Arrays.sort(pItems);
		return pItems;
	}
	
	
	/*
	 * ************* Backwards File Compatibility ************
	 */
	
	/**
	 * Reads a type ID from the given APD file, converting appropriately
	 * to the current version's type ID.
	 * @param file file to read from; type ID must be the next item in the file
	 * @param version version of the APD file being read
	 * @return the external ID of the type
	 */
	static public SB_VarType readAPDType(ObjectInputStream in, int version) throws IOException
	{
		int rawType = in.readInt();
		SB_VarType type = SB_VarType.kNoSuchName;
		boolean converted = false;

		if (version >= 11)
		{
			type = SB_VarType.getTypeFromInt(rawType);
		}
		else 
		{
			if (version <= 3)
			{
				if (rawType == 5)	// previous index for any
				{
					type = SB_VarType.kAny;
					converted = true;
				}
			}
			if (!converted)
				type = convertOldTypeId(rawType);
		}

		return type;
	}

	/**
	 * Reads a type ID from the given SBP file, converting appropriately
	 * to the current version's type ID.
	 * @param file file to read from; type ID must be the next item in the file
	 * @param version version of the SBP file being read
	 * @return the external ID of the type
	 */
	static public SB_VarType readSBPType(ObjectInputStream file, int version) throws IOException
	{
		int rawType = file.readInt();
		SB_VarType type = null;

		if (version >= 18)
		{
			type = SB_VarType.getTypeFromInt(rawType);
		}
		else 
		{
			type = convertOldTypeId(rawType);
		}

		return type;
	}

	/**
	 * Reads a type ID from the given TPD file, converting appropriately
	 * to the current version's type ID.
	 * @param file file to read from; type ID must be the next item in the file
	 * @param version version of the TPD file being read
	 * @return the external ID of the type
	 */
	static public SB_VarType readTPDType(ObjectInputStream in, int version) throws IOException
	{
		int rawType = in.readInt();
		SB_VarType type = null;
		boolean converted = false;

		if (version >= 4)
		{
			type = SB_VarType.getTypeFromInt(rawType);
		}
		else 
		{
			type = convertOldTypeId(rawType);
		}

		return type;
	}

	/**
	 * Reads a type ID from the given BTN file, converting appropriately
	 * to the current version's type ID.
	 * @param file file to read from; type ID must be the next item in the file
	 * @param version version of the BTN file being read
	 * @return the external ID of the type
	 */
	static public SB_VarType readBTNType(ObjectInputStream file, int version) throws IOException
	{
		int rawType = file.readInt();
		SB_VarType type = null;

		if (version >= 16)
		{
			type = SB_VarType.getTypeFromInt(rawType);
		}
		else 
		{
			type = convertOldTypeId(rawType);
		}

		return type;
	}

	/**
	 * Converts a type ID into the old type id.  This is for XML compatibility
	 * with the Java editor until it switches over to the new type ID system.
	 * @param id the new type id to convert
	 * @return the corresponding old type id
	 */
	public int convertToOldId(SB_VarType id){
		if (id.equals(SB_VarType.kInteger))
			return OLD_INTEGER_VT;
		else if (id.equals(SB_VarType.kBoolean))
			return OLD_BOOLEAN_VT;
		else if (id.equals(SB_VarType.kAny))
			return OLD_ANY_VT;
		else
			return id.getState();	// all other types are unchanged
	}

	
	/*
	 * ************* helper methods *************
	 */
	
	/**
	 * @param checkDeleted if true, will retrieve the type with that id even if it has been deleted
	 */
	private TypeInfo getTypeInfoById(SB_VarType id, boolean checkDeleted)
	{
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (type._id.equals(id) && (checkDeleted || !type._deleted))
				return type;
		}
		
		return null;
	}
	
	/**
	 * getTypeInfoById(id, false)
	 * @see #getTypeInfoById(SB_VarType, boolean)
	 */
	private TypeInfo getTypeInfoById(SB_VarType id){
		return getTypeInfoById(id, false);
	}
	
	private TypeInfo getTypeInfoByName(String name, boolean checkDeleted)
	{
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if (type._name.equals(name) && (checkDeleted || !type._deleted))
				return type;
		}
		
		return null;
	}
	
	private TypeInfo getTypeInfoByName(String name){
		return getTypeInfoByName(name, false);
	}
	
	private TypeInfo getTypeInfoByPackage(String packageName, boolean checkDeleted)
	{
		for (int i = 0; i < _types.size(); i ++)
		{
			TypeInfo type = (TypeInfo) _types.get(i);
			if ((type._typeFlags & ETypeType.kClassType.getState()) > 0 &&	// only classes have a package
				(type._package.equals(packageName)) && 
				(checkDeleted || !type._deleted))
				return type;
		}
		
		return null;
	}
	
	private TypeInfo getTypeInfoByPackage(String packageName){
		return getTypeInfoByPackage(packageName, false);
	}

	private void deleteType(int index, boolean permanent)
	{
		TypeInfo type = (TypeInfo) _types.get(index);
		if (permanent || (type._typeFlags & ETypeType.kClassType.getState()) == 0)
		{
			// back up the next-id counter if necessary
			if (_nextClassBaseId == type._id.getState() + 1)
				--_nextClassBaseId;
			else if (_nextClassUserId == type._id.getState() + 1)
				--_nextClassUserId;

			_types.remove(index);
		}
		else
		{
			// don't actually delete the type because we want to reserve its
			// id in perpetuity
			type._deleted = true;
		}
		
		// notifies the listeners that the type was removed
		notifyTypeRemoved(type);
	}
	
	private void deleteType(int index){
		deleteType(index, false);
	}

	private boolean isVarComboItem(TypeInfo type, boolean isConstant, boolean isParam)
	{

		if (type._deleted)
			return false;

		if ((type._typeFlags & (ETypeType.kSimBionicType.getState() | ETypeType.kEnumType.getState() | ETypeType.kClassType.getState())) > 0)
		{	
			if (isConstant)
			{
				// constant
				return ((type._validFlags & ETypeValid.kForConst.getState()) > 0);
			}
			else if (isParam)
			{
				// action/predicate/behavior param
				return ((type._validFlags & ETypeValid.kForParam.getState()) > 0);
			}
			else
			{
				// local or global variable
				return ((type._validFlags & ETypeValid.kForVar.getState()) > 0);
			}
		}
		
		return false;
	}
	
	private boolean isClassComboItem(TypeInfo type, boolean isParam)
	{
		if (type._deleted)
			return false;

		if ((type._typeFlags & (ETypeType.kJavaType.getState() | ETypeType.kClassType.getState())) > 0)
		{
			if (isParam)
			{
				// method param
				return ((type._validFlags & ETypeValid.kForParam.getState()) > 0);
			}
			else
			{
				// method field
				return ((type._validFlags & ETypeValid.kForVar.getState()) > 0);
			}
		}
		
		return false;
	}
	
	private boolean isReturnValueComboItem(TypeInfo type, boolean isMethod)
	{
		
		if (type._deleted)
			return false;

		if ((type._validFlags & ETypeValid.kForRetVal.getState()) > 0)
		{
			if (isMethod)
			{
				return ((type._typeFlags & (ETypeType.kJavaType.getState() | ETypeType.kClassType.getState())) > 0);
			}
			else
			{
				return ((type._typeFlags & (ETypeType.kSimBionicType.getState() | ETypeType.kEnumType.getState() | ETypeType.kClassType.getState())) > 0);
			}
		}
		
		return false;
	}
	
	private TypeInfo cloneTypeInfo(TypeInfo info){
		TypeInfo copy = new TypeInfo();
		copy._name = info._name;
		copy._package = info._package;
		copy._id = info._id;
		copy._typeFlags = info._typeFlags;
		copy._validFlags = info._validFlags;
		copy._isBase = info._isBase;
		copy._deleted = info._deleted;
		return copy;
	}
	
	private int getNumericTypeValue(SB_VarType type)
	{
		if (type.equals(SB_VarType.kByte))
			return 0;
		else if (type.equals(SB_VarType.kShort))
			return 1;
		else if (type.equals(SB_VarType.kInteger))
			return 2;
		else if (type.equals(SB_VarType.kEntity))
			return 3;
		else if (type.equals(SB_VarType.kFloat))
			return 4;
		else if (type.equals(SB_VarType.kDouble))
			return 5;
		else if (type.getState() >= SB_VarType.kCLASS_START.getState())
		{
			String fullName = getTypeInfoById(type)._package;
			if ("java.lang.Byte".equals(fullName))
				return 0;
			else if ("java.lang.Short".equals(fullName))
				return 1;
			else if ("java.lang.Integer".equals(fullName))
				return 2;
			else if ("java.lang.Long".equals(fullName))
				return 3;
			else if ("java.lang.Float".equals(fullName))
				return 4;
			else if ("java.lang.Double".equals(fullName))
				return 5;
			else
				return -1;
		}
		else
			return -1;
	}

	
	/*
	 * ************* type change listener methods *************
	 */
	
	public void addTypeChangeListener(SB_TypeChangeListener l){
		_typeChangeListeners.add(l);
	}
	
	public void removeTypeChangeListener(SB_TypeChangeListener l){
		_typeChangeListeners.remove(l);
	}
	
	public void removeAllTypeChangeListeners(){
		_typeChangeListeners.clear();
	}
	
	protected void notifyTypeAdded(TypeInfo typeInfo){
		for (int i = 0; i < _typeChangeListeners.size(); i ++)
			((SB_TypeChangeListener)_typeChangeListeners.get(i)).typeAdded(this, typeInfo);
	}
	
	protected void notifyTypeRemoved(TypeInfo typeInfo){
		for (int i = 0; i < _typeChangeListeners.size(); i ++)
			((SB_TypeChangeListener)_typeChangeListeners.get(i)).typeRemoved(this, typeInfo);
	}

	protected void notifyTypeChanged(TypeInfo oldTypeInfo, TypeInfo newTypeInfo){
		for (int i = 0; i < _typeChangeListeners.size(); i ++)
			((SB_TypeChangeListener)_typeChangeListeners.get(i)).typeChanged(this, oldTypeInfo, newTypeInfo);
	}
	
	
	private ArrayList _types;	// ArrayList<TypeInfo>
	
	private int _nextClassBaseId;
	
	private int _nextClassUserId;
	
	private ArrayList _typeChangeListeners;
}
